﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Chat
{
    /// <summary>
    /// Argumants for event, risen by the CommandReceiverThread instanse,
    /// upon problems detected in communication between the chat client and server.
    /// </summary>
    public class DisconnectedExEventArgs : EventArgs
    {
        /// <summary>
        /// The exception, containing the information about the kind the
        /// communication.
        /// </summary>
        public Exception DisconnectedException { get; protected set; }
        public DisconnectedExEventArgs(Exception disconnectedException)
        {
            DisconnectedException = disconnectedException;
        }
    }

    /// <summary>
    /// Arguments for event, risen by the CommandReceiverThread instanse,
    /// upon recept of chat command.
    /// If the event handler decides that, it can terminate the receiver thread
    /// of the CommandReceiverThread instanse, by seting the Terminate property
    /// to false and the thread will terminate after the event handler function
    /// returns to the caller, which is the exactly the thread to terminate.
    /// See CommandReceiverThread implementation for details, namely
    /// the ReceiveCommands method.
    /// </summary>
    public class CmdReceivedEventArgs : EventArgs
    {
        public object CommandReceived { get; protected set; }
        public bool Terminate { get; set; }

        public CmdReceivedEventArgs(object commandReceived)
        {
            CommandReceived = commandReceived;
            Terminate = false;
        }
    }

    /// <summary>
    /// The main aspect of this class is receiving objects from abstract receiver
    /// in a separate thread and generating events upon objects received
    /// or problems occurred on receive.
    /// While it is not evident from the class implementation, the semantics says
    /// that the class will be used for receving chat commands (either server commands
    /// from chat server, or client commands from chat client).
    /// Also, it's supposed, that behind the abstract receiver stands some kind of
    /// communication channel between the client & server. Thus any exception thrown
    /// by the receive operation of the abstract receiver, is treated as
    /// a communication problem and a special Disconnected event is risen.
    /// </summary>
    public class CmdReceiverThread
    {
        protected Thread ReceiverThread { get; set; }
        protected ICmdReceiver Receiver { get; set; }
        private bool terminate = false;

        public event EventHandler<CmdReceivedEventArgs> CommandReceived;
        public event EventHandler<DisconnectedExEventArgs> Disconnected;

        public CmdReceiverThread(ICmdReceiver receiver)
        {
            Receiver = receiver;

            ReceiverThread = new Thread(CmdReceiverThreadMain);
            ReceiverThread.IsBackground = true;
        }

        public virtual void Start()
        {
            ReceiverThread.Start();
        }

        public virtual void Stop()
        {
            if (Thread.CurrentThread.ManagedThreadId != ReceiverThread.ManagedThreadId)
            {
                if (ReceiverThread.IsAlive)
                    ReceiverThread.Abort();
            }
            else
            {
                terminate = true;
            }
        }

        public virtual void StopGraceful()
        {
            terminate = true;
        }

        protected void CmdReceiverThreadMain()
        {
            try
            {
                ReceiveCommands();
            }
            catch (ThreadAbortException)
            {
            }
            catch (Exception ex)
            {
                if (!terminate)
                    OnDisconnected(new DisconnectedExEventArgs(ex));
            }
        }

        protected virtual void ReceiveCommands()
        {
            while (!terminate)
            {
                object cmd = Receiver.ReceiveCommand();
                CmdReceivedEventArgs evtArgs = new CmdReceivedEventArgs(cmd);

                OnCommandReceived(evtArgs);

                if (evtArgs.Terminate)
                    terminate = true;
            }
        }

        protected virtual void OnCommandReceived(CmdReceivedEventArgs e)
        {
            if (CommandReceived != null)
                CommandReceived(this, e);
        }

        protected virtual void OnDisconnected(DisconnectedExEventArgs e)
        {
            if (Disconnected != null)
                Disconnected(this, e);
        }
    }
}
